Skip to content

build: move the provisioning tail out of build and into provision - #251

Open
mobileoverlord wants to merge 5 commits into
mainfrom
jschneck/runtime-build-ota-flag
Open

build: move the provisioning tail out of build and into provision#251
mobileoverlord wants to merge 5 commits into
mainfrom
jschneck/runtime-build-ota-flag

Conversation

@mobileoverlord

@mobileoverlord mobileoverlord commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Owner: this PR is mine (the fast-rebuilds series), not the feeds series.
Both series push under the same account, so authorship does not distinguish
them.

Breaking change to what avocado build produces. Read the first section
before the diff.

What changes

avocado build and avocado runtime build now produce the deployable set:
the runtime manifest, the images and the elements. That is exactly what
avocado deploy and avocado connect upload read, and all either has ever
read.

The var image is a provisioning artifact — nothing but avocado provision
consumes it — so provision now builds it itself, at its entry point, ahead of
the avocado-provision-<arch> hook. Stone is untouched: it is invoked
exactly as before and handed exactly the inputs it has always been handed.

The OS bundle stays in build, along with the avocado-build-<target> hook
and the os_bundle manifest patch. See "Correction" below — an earlier revision
of this PR moved them too, and that broke OTA.

Migration

A pipeline that runs avocado build and then flashes will find no var image.
Run avocado provision, which produces it.

There is deliberately no flag to restore the old behaviour. A flag on
build would be asking the user a question whose answer is always "whatever the
next command needs" — and the flag design that was prototyped here made that
concrete: it needed a stamp for provision to interrogate, and an else-branch
that deleted both the artifacts and that stamp, because otherwise a plain
rebuild after a provisionable one left a stamp still matching unchanged inputs
while the artifacts it vouched for were gone. Moving the work removes the
question, the stamp and the failure mode together.

Why it was safe to move, checked rather than assumed

The section is ~200 lines, and its coupling to the build script turned out to be
small enough to relocate rather than reconstruct:

  • Eight shell variables. Two (AVOCADO_PREFIX, AVOCADO_SDK_PREFIX) come
    from the entrypoint prologue that every container run already sources. The
    rest are deterministic paths, and the section now emits them itself, so it is
    self-contained.
  • Six interpolated sections, all config-derived: Docker priming, mkfs flags,
    compression, losetup post-creation, device-tree overlays, LUKS room.

Nothing depended on build-time state that only the build script held. provision
already ran container commands and already read the var and stone config, so the
section slots in ahead of the existing hook call.

Shape

It moves to commands/runtime/var_image.rs behind one entry point taking an
explicit VarImageContext. That context is the point, not just tidiness: it is
precisely the set of things a portable provisioning bundle has to carry, so a
later avocado provision --bundle <path> becomes a matter of sourcing the
context from a bundle instead of from a project.

Tests

The behaviour moved with the code rather than being dropped:

  • var-encrypt leaves room for the LUKS header, and a plaintext runtime keeps the
    tight image — now asserted against the rendered section.
  • Device-tree overlays reach stone --overlay, and are inert when none are
    declared.
  • The section defines every variable it reads that the prologue does not supply
    — the property a bundle depends on.
  • bash -n on the rendered section, which is the check that catches a botched
    heredoc.
  • build keeps a test that it emits none of the tail, anchored on
    start-of-line commands because "stone bundle" also appears in a comment that
    legitimately stays.

Addressed from Copilot review 1

All four findings were real; two of them were bugs that would have shipped.

  • Wrong paths in the moved section. I wrote the preamble by hand and guessed:
    $AVOCADO_PREFIX/output/runtimes/<rt>/var-staging and an active symlink under
    lib/avocado/runtimes/. build actually writes $AVOCADO_PREFIX/runtimes/<rt>
    and links lib/avocado/active. It also read AVOCADO_NS_UUID and
    AVOCADO_IMAGES_DIR from the environment, which build used to export and no
    longer does. The section now resolves the manifest the way the hash collection
    does — the active symlink with a find fallback, because provision does not
    know the BUILD_ID that names the directory — and exports what the python step
    reads. the_section_uses_the_paths_build_actually_writes pins all of it,
    including that $AVOCADO_PREFIX/output appears nowhere.
  • A leftover provisionable gate and flag. An earlier iteration of this work
    put a --provisionable flag on build and gated provision on a stamp. That
    design was dropped in favour of moving the work, but the flag, the
    StampCommand::Provisionable variant and the gate survived the rewrite. Since
    build no longer writes that stamp, the gate would have refused every
    provision. All of it is gone.
  • Stone paths hard-coded alongside an existing OUTPUT_DIR. Now derived from it.
  • Flag help text describing behaviour that no longer exists — removed with the flag.

Correction: the OS bundle is OTA payload, not a provisioning artifact

An earlier revision of this PR moved the whole tail to provision. That was
wrong and was caught on hardware (QCS9075, UKI platform), not by any test here:

  • On UKI platforms the avocado-build-<target> hook is the kernel and
    initramfs — it assembles the per-slot uki_<slot>.efi that the stone update
    block names. With the hook only in provision, build emitted no boot
    artifacts at all.
  • os-bundle.aos is the OTA payload, and stone bundle is also what populates
    os_artifacts. Without them the runtime manifest carried no OS payload.

Observed: avocado deploy reported success, updated the extensions, switched the
active runtime, and left the board on its old kernel with /var still
plaintext. Nothing reported that the OS had not been updated. That also made an
initramfs-only opt-in like var.encrypt unreachable over OTA, since its marker
lives inside the initramfs.

The rule now applied: anything an OTA requires is at the tail of runtime build; anything only provisioning consumes is at the start of provision.

Ordering turned out to matter as much as placement. The manifest id is minted by
manifest_section during build, so a bundle produced later by a separate
command gets patched into a document whose id has not changed — a device already
holding that id skips the update, including the os_bundle that appeared
inside it. Measured: a manifest genuinely carrying os_bundle still produced
"Runtime already at target version, nothing to do." Building the bundle in the
same run that mints the id removes that failure mode.

This also fixes sign_amf: command not found, which the moved code introduced:
the OTA half calls sign_amf and deliberately does not define it, inheriting the
helper from the build script. In provision that call resolved to nothing and
provisioning died after the bundle was built. A test now asserts the helper is
defined before the call in the rendered script, and that the re-sign follows the
patch it exists for.

The one inference I could not test on hardware

stone bundle needs a var partition size: every platform manifest declares var
as expand: "true" with no size, which is exactly what --partition-size
overrides, and stone fails hard without it — verified by running it:
partition 'var' omits size; no --partition-size override was supplied.

The var image does not exist at build time, so the size is now declared from the
staged tree plus headroom rather than measured from the image. I checked that
nothing acts on it: no provisioning script reads the bundle (avocado-provision-<target>
has zero references to it, and the UFS flow injects raw images), an OTA never
repartitions, and the partition expands at provision time. If a
provision-from-bundle path is ever added, that number becomes real and must come
from the image — which is written at the call site.

Split

commands/runtime/var_image.rs now exposes two halves over one context:
render_ota_tail (spliced into the build script, inherits its variables) and
render_var_image (standalone, emits its own preamble). Tests assert the split
in both directions, because getting it backwards is not a build failure — it is
a deploy that reports success and leaves the device on its old OS.

@mobileoverlord
mobileoverlord force-pushed the jschneck/runtime-build-ota-flag branch from 880b7c2 to e15e385 Compare September 7, 2026 17:27
@mobileoverlord mobileoverlord changed the title runtime build: skip the provisioning tail build: move the provisioning tail out of build and into provision Sep 7, 2026
@mobileoverlord
mobileoverlord marked this pull request as ready for review September 7, 2026 17:32
Copilot AI lite review requested due to automatic review settings September 7, 2026 17:32

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The moved provisioning tail currently has path/environment mismatches and provision still enforces a “provisionable build” gate with misleading messaging, which can cause provisioning failures or unnecessary rebuilds.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR relocates the “provisioning tail” (var image + stone OS bundle + Docker priming) out of runtime build and into runtime provision, introducing a dedicated renderer (commands/runtime/var_image.rs) and new stamp semantics to distinguish deployable build outputs from provisioning-only artifacts.

Changes:

  • Add commands/runtime/var_image.rs to render/run the provisioning tail from provision (with unit tests around rendering and bash validity).
  • Introduce a new Provisionable stamp command/requirement and wire it into runtime build/provision flows.
  • Add a --provisionable CLI flag plumbing and document the breaking change in CHANGELOG.md.
File summaries
File Description
src/utils/stamps.rs Adds Provisionable stamp command plus constructors/requirements and tests.
src/main.rs Adds --provisionable flags for build/runtime build and wires them into command builders.
src/commands/runtime/var_image.rs New module rendering the provisioning tail script + tests.
src/commands/runtime/provision.rs Runs the rendered provisioning tail before the provision hook; adds stamp gate helper/tests.
src/commands/runtime/mod.rs Exposes the new var_image module.
src/commands/runtime/build.rs Removes the provisioning tail from build output; optionally writes a provisionable stamp.
src/commands/build.rs Plumbs provisionable through top-level build into runtime builds.
CHANGELOG.md Documents the breaking change and migration guidance.
Review details

Suppressed comments (2)

src/commands/runtime/provision.rs:225

  • This gate refuses provisioning unless a provisionable stamp exists, but this command now builds the var image and OS bundle itself later in execute() (via var_image::render), so the refusal can block an otherwise-valid avocado provision. The error message is also misleading because it claims the artifacts are built by avocado build --provisionable, but the artifacts are produced here in provision.
            // `avocado build` produces the deployable set only. The var image
            // and the OS bundle — the artifacts stone flashes — come from the
            // provisioning tail, which runs only under `--provisionable` and
            // records its own stamp. Require that stamp, and require it current:
            // a plain rebuild since then deleted those artifacts and the stamp

src/main.rs:1786

  • Same issue as the top-level build command: this --provisionable help text says it builds provisioning artifacts, but the provisioning artifacts are now built during runtime provision. The CLI docs should reflect what the flag actually does (or the flag should be removed if it’s no longer needed).
        /// Also build the provisioning artifacts: the var image, the OS bundle
        /// and Docker priming.
        ///
        /// Without this, a build produces only what `avocado deploy` and
        /// `avocado connect upload` consume. `avocado provision` requires a build
        /// that used this flag.
  • Files reviewed: 8/8 changed files
  • Comments generated: 4
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/commands/runtime/provision.rs Outdated
Comment thread src/commands/runtime/var_image.rs Outdated
Comment thread src/commands/runtime/var_image.rs
Comment thread src/main.rs Outdated
@mobileoverlord
mobileoverlord force-pushed the jschneck/runtime-build-ota-flag branch from e15e385 to 9c8b901 Compare September 7, 2026 17:53
@mobileoverlord

Copy link
Copy Markdown
Contributor Author

@copilot review

@mobileoverlord

Copy link
Copy Markdown
Contributor Author

All four findings from the review are addressed as of 9c8b901. Verified against the pushed head rather than the working tree:

Finding State at 9c8b901
Wrong var-staging / active-symlink paths VAR_DIR=$AVOCADO_PREFIX/runtimes/$RUNTIME_NAME/var-staging, manifest resolved via lib/avocado/active with a find fallback. $AVOCADO_PREFIX/output now appears exactly once in the file — in the assertion that it must not appear.
AVOCADO_NS_UUID / AVOCADO_IMAGES_DIR unset Both exported by the section. build used to export them and no longer runs.
provisionable gate with misleading message Gone. has_provisionable_build, the --provisionable flag and StampCommand::Provisionable were leftovers from a design that was dropped in favour of moving the work; since build no longer writes that stamp, the gate would have refused every provision.
Stone paths hard-coded beside OUTPUT_DIR Derived from OUTPUT_DIR.

Two of those were bugs that would have shipped, and the gate one would have broken provisioning outright — thanks.

New test the_section_uses_the_paths_build_actually_writes pins the paths, the exported environment, and the symlink-then-search resolution, so this cannot regress silently. CI green.

Note for a re-review: the review summary above was re-timestamped by the force-push but its inline comments are outdated and its body is unchanged, so it reads as if nothing was addressed.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Two newly added doc comments are misattached to the runs_on option, producing misleading user-facing help/docs for remote execution.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 6/6 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread src/commands/build.rs Outdated
Comment thread src/commands/runtime/build.rs Outdated
@mobileoverlord
mobileoverlord force-pushed the jschneck/runtime-build-ota-flag branch from 9c8b901 to 23355d6 Compare September 7, 2026 21:00
@mobileoverlord

Copy link
Copy Markdown
Contributor Author

Review round 2 addressed in 23355d6; all threads replied to and resolved. Verified against the pushed head, not a local tree:

# Finding State at 23355d6
1 var-staging / active-symlink paths, missing AVOCADO_NS_UUID + AVOCADO_IMAGES_DIR fixed — $AVOCADO_PREFIX/output now appears once in the file, in the assertion that it must not appear
2 provisionable gate would refuse every provision removed
3 stone paths hard-coded beside OUTPUT_DIR derived from it
4 --provisionable help describing behaviour that does not exist flag removed; main.rs is byte-identical to main
5 orphaned doc block adopted by runs_on in commands/build.rs removed
6 same in commands/runtime/build.rs removed

5 and 6 were caused by removing the provisionable field with a regex that matched the field but not its doc comment, so the block silently attached to the next field. That compiles and tests green — the only symptom is wrong user-facing help. I audited the rest of the commit for the same pattern and found none.

Gate: full cargo test (all 18 targets, not just --lib) and clippy --all-targets clean on this branch and on every branch stacked above it. CI green.

Two Copilot re-review requests have been accepted (201) since this push without a review arriving, so this comment is the record in the meantime.

…mage in provision

`avocado build` produced a var image and an OS bundle. The var image is only ever
read by `provision`; the OS bundle is the OTA payload. The first cut of this
change moved both to `provision`, which broke OTA on UKI platforms: the
`avocado-build-<target>` hook *is* the kernel and initramfs there, so `build`
emitted no boot artifacts, and the runtime manifest carried no `os_bundle`.
Hardware-verified on QCS9075 — `deploy` reported success, updated the
extensions, and left the board on its old kernel with /var still plaintext.
Nothing reported that the OS had not been updated.

The split is by consumer, not by phase:

- Build tail: the stone include paths, device-tree overlays, the platform build
  hook, `stone bundle`, the `os_bundle` manifest patch and the re-sign after it.
  All of it produces or names OTA payload. It is spliced into the build script
  and inherits that script's variables and its `sign_amf` helper — which also
  fixes `sign_amf: command not found`, where the moved call resolved to nothing
  and provisioning died after the bundle was already built.
- Provision: Docker priming and the var image. Verified that no provisioning
  script reads the bundle; `avocado-provision-<target>` and the UFS flow both
  inject raw images.

Ordering matters as well as placement. The manifest id is minted by
`manifest_section`, so a bundle produced afterwards by a separate command is
patched into a document whose id has not changed — a device already holding that
id skips the update, including the `os_bundle` that appeared inside it. Building
the bundle in the same run that mints the id makes every build's manifest carry
its own OS payload.

`stone bundle` fails without a var partition size, because platform manifests
declare `var` as `expand: "true"` with no size — that is the case
`--partition-size` exists for. The var image does not exist at build time, so the
size is declared from the staged tree with headroom, documented at the call site
along with the condition that would make it a real number.
@mobileoverlord
mobileoverlord force-pushed the jschneck/runtime-build-ota-flag branch from 23355d6 to 1b2175d Compare September 8, 2026 13:03
`$AVOCADO_PREFIX/output/runtimes/<rt>` and `$AVOCADO_PREFIX/runtimes/<rt>` are
two different trees. The first is where the SDK's stone tooling looks
(`$AVOCADO_STONE_DATA_DIR`), where the device-tree overlay staging writes, and
where `provision` keeps its state. `STONE_BUILD_DIR` and `STONE_AOS_OUTPUT`
belong there and always did.

A review read them as duplicating `OUTPUT_DIR` and "easy to drift", and I
repointed them without checking that the two paths were the same tree. They are
not. The build then wrote its stone outputs to the new location while
`provision` kept unpacking the bootfiles tarball still sitting at the old one —
so a flash wrote an OLD GPT and OLD firmware alongside a newly built system
image, var image and ESP. On a build that had renamed the var partition label,
the new initramfs looked for a label the freshly written GPT did not have and
every boot ended in initrd emergency, with nothing pointing at the cause.

`rm -rf "$STONE_BUILD_DIR"` was cleaning the wrong directory too, which is why
the stale copy survived rebuild after rebuild.

Found on hardware. Pinned by a test asserting both paths and asserting they are
not under `$OUTPUT_DIR`, because the next reviewer will read them the same way.
Each branch in this stack appended its own `### Changed` / `### Fixed` block, so
by the top of the stack Unreleased carried several of each. That breaks
Keep-a-Changelog structure and renders as repeated sections.

Merged per section, preserving entry order. No entry text changed.
@nicksinas

Copy link
Copy Markdown
Contributor

The problem

avocado build fails on every target whose stone manifest names a var
image. That is all four targets we test.

[INFO] Running SDK lifecycle hook 'avocado-build' for 'dev'.
[INFO] Running stone validate.
[ERROR] Validation failed. 1 file(s) not found:
  device: rootdisk, image: var
    avocado-image-var-qemuarm64.btrfs
[ERROR] Failed to build runtime 'dev': Failed to build complete image

This PR moves the var image to provision. But avocado-build-<target>
runs stone validate and stone create over the whole manifest, and every
platform manifest names a var image. The hook needs the file before
provision creates it.

The PR says that stone gets the same inputs as before. On qemuarm64 stone
gets one input less, and it stops.

What already works

Two parts of the split need no change:

  • stone bundle gets --partition-size var=$STONE_VAR_SIZE from this PR.
    It does not need the file.
  • var is not in update.os_artifacts. Only boot and rootfs are. The
    OTA payload is correct without a var image.

Why a derived manifest does not work

One option is to give the hook a manifest with var removed. The var
partition is expand: "true" and has no size. Stone still needs a value
for the size, and the hook passes no --partition-size of its own. A
derived manifest needs an invented size, and the disk layout can then
differ between stone create and stone provision.

The fix, part 1 — declare the partition at build

Before the hook runs, the build script creates a sparse file at the path
that stone reads:

STONE_VAR_PLACEHOLDER="$STONE_INPUT_DIR/avocado-image-var-$TARGET_ARCH.btrfs"
if [ ! -f "$STONE_VAR_PLACEHOLDER" ]; then
    mkdir -p "$STONE_INPUT_DIR"
    truncate -s "$STONE_VAR_SIZE" "$STONE_VAR_PLACEHOLDER"
fi

The file is sparse and uses the declared size that stone bundle already
gets. The hook can then resolve the partition and set its size. The costly
work stays in provision: docker priming, the subvolumes, and
mkfs.btrfs.

The if [ ! -f ] guard is necessary. A provision in the same project
leaves a real var image at this path.

The fix, part 2 — refresh what stone provisions from

Part 1 alone puts the placeholder on the device. avocado-provision-<target>
adds one input path of its own:

-i $AVOCADO_PREFIX/output/runtimes/<rt>/stone

It reads the runtime dir only to stat the var image for
--partition-size. So stone takes the var content from the stone build
dir. stone create filled that dir during build, when the only var image
was the placeholder. Stone resolves first-match-wins, so the placeholder
wins.

The board then gets a var partition of the correct size that holds only
zeros:

EXT4-fs (mmcblk0p7): VFS: Can't find ext4 filesystem
erofs: (device mmcblk0p7): cannot find valid erofs superblock
[FAILED] Failed to mount /var.
You are in emergency mode.

The build dir held a var image of 214548876 bytes that was 0 bytes on disk.

So provision overwrites that copy with the image it just built, after the
var section and before the hook. It uses a hard link, because both trees
are under $AVOCADO_PREFIX. If that crosses a mount, it copies the file.

This does not go in the var section. That half must stay portable for
provision --bundle, and a bundle carries no build tree. The test
the_var_half_is_self_contained says so, and it caught the first attempt.

Limits

This is a stopgap. The hook does two different jobs: it stages OTA
artifacts, and it assembles the provisioning disk. Two changes remove the
need for a placeholder:

  • Split the hook in meta-avocado. The build half stages OTA artifacts. The
    provision half assembles the disk.
  • Give stone a scope flag, so build validates only the images it produces.

The failure above is an argument for one of these. Two files with the same
name sit in two trees. The one that stone resolves first is not visible in
the code. A reader of build.rs cannot see it.

@nicksinas

Copy link
Copy Markdown
Contributor

Changes

Branch cli-rc4-fix-251, two commits, on top of a local branch that merges
the 11 open non-draft CLI PRs.

9dfd336  fix(build): declare the var partition for the platform build hook
44e4149  fix(provision): refresh the var image stone provisions from

src/commands/runtime/var_image.rs (+65)

  1. render_ota_tail(), before the AVOCADO_BUILD_HOOK call. Adds the
    STONE_VAR_PLACEHOLDER block. Placed after STONE_VAR_SIZE and
    STONE_INPUT_DIR, which it reads.
  2. New test the_ota_half_declares_a_var_placeholder_before_the_build_hook.
    It asserts four things:
    • The placeholder comes before the hook.
    • The path is the path that provision writes the real image to.
    • The file is sparse and uses $STONE_VAR_SIZE.
    • The guard on absence is present.

src/commands/runtime/provision.rs (+79)

  1. create_provision_script(). Adds the refresh of the stone build dir
    copy, between the var section and the hook.
  2. New test the_provision_script_refreshes_the_var_image_stone_reads. It
    asserts both paths, and that the refresh sits between the var section
    and the hook. The order is the whole point of the change.

No other source file changes. No changes to meta-avocado.

Not part of the fix

Cargo.toml and Cargo.lock set the version to 1.0.0-rc.4-dev.fix251.
This is a local marker that lets a shim tell the builds apart. Do not merge
this change.

Test results

  • 1883 lib tests pass. 1894 bin tests pass.
  • All 17 integration test binaries pass.
  • 5 tests fail. The same 5 fail on the unmerged branch of this PR. Four are
    BSD sed against GNU sed on macOS. One is a qga.service [Install]
    fixture. This change does not affect them.
  • both_halves_are_valid_bash passes, so the new shell parses.

Validation

Build-level A/B on qemuarm64, release 2024, channel edge, with the dev
reference project:

  • Before the fix: install passes. build fails at stone validate.
  • With avocado 1.0.0-rc.3: install passes with -f, and build passes.
    The failure comes from this PR and not from the machine.

On hardware:

  • With part 1 only: build passes. The board stops in emergency mode with
    an unmountable /var.
  • With both parts: build and provision pass. The board boots and /var
    mounts.

avocado-build-<target> runs stone validate and stone create over the whole
manifest, and every platform manifest names a var image. Moving the var image
to provision left the hook with one input fewer, and it stops:

    [ERROR] Validation failed. 1 file(s) not found:
      device: rootdisk, image: var

A derived manifest with var removed does not work: the partition is
expand: "true" with no size, stone still needs one, and the hook passes no
--partition-size of its own, so the layout could differ between stone create
and stone provision.

Creates a sparse placeholder at the size stone bundle is already given. The
hook can resolve the partition; the costly work stays in provision. Guarded on
absence because a provision in the same project leaves a real image here.

This half is NOT sufficient alone -- see the following commit.
With the placeholder alone the board gets a var partition of the right size
holding zeros:

    erofs: (device mmcblk0p7): cannot find valid erofs superblock
    [FAILED] Failed to mount /var.  You are in emergency mode.

Two files of this name exist under AVOCADO_PREFIX and stone resolves
first-match-wins across its -i dirs, so which reaches the device is not
visible from the code that flashes it. stone create filled the build dir
during build, when the only var image was the placeholder.

Replaces that copy with the real image, between the var section and the hook.
Hard link, copying if it crosses a mount. Not in the var section: that half
stays portable for provision --bundle, which carries no build tree.

Fails closed -- this shell has no set -e, and a silent skip ships a device
that drops to an emergency shell on first boot.
@mobileoverlord

Copy link
Copy Markdown
Contributor Author

Pushed two commits fixing the var-image staging this PR introduced, both from @nicksinas's diagnosis above — thanks for the writeup, it named the failure and the fix precisely.

6b9e061 fix(build): declare the var partition for the platform build hook
509f5cc fix(provision): refresh the var image stone provisions from

Part 1 is the sparse STONE_VAR_PLACEHOLDER at $STONE_VAR_SIZE, created before the build hook and guarded on ! -f so a real image from a prior provision is not truncated back to zeros.

Part 2 replaces the stone build dir's copy with the real image, between the var section and the hook, hard link with a copy fallback. Kept out of the var section so provision --bundle stays portable, per the_var_half_is_self_contained.

One deviation from the writeup: the refresh fails closed. That shell runs without set -e, and every way of skipping it quietly — missing source, unwritable target — ships a device that drops to an emergency shell on first boot, so a missing source is an explicit exit 1 rather than a silent no-op.

Two tests, both asserting the ordering, which is the whole point:
the_ota_half_declares_a_var_placeholder_before_the_build_hook
the_provision_script_refreshes_the_var_image_stone_reads

Confirmed on hardware, on a different board than the report

An RB3 Gen 2 hit the same root cause with a different symptom — build passed, and provision died later:

Injecting runtime /var as avocado-image-var-rb3gen2.btrfs
cp: cannot stat '.../stone/avocado-image-var-rb3gen2.btrfs': No such file or directory

The var image was at $AVOCADO_PREFIX/runtimes/<rt>/ while the provision script reads $AVOCADO_STONE_DATA_DIR. With both parts applied, provision gets past it and all three staged copies are real btrfs, hard-linked.

The warning about part 1 alone is worth repeating: a placeholder-only fix flashes successfully and leaves the board in emergency mode with a correctly-sized, zero-filled /var. Both halves or neither.

Note the Cargo.toml/Cargo.lock version marker in the original branch is deliberately not included.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants